mod ast;
mod error;
mod grammar;
mod lexer;
pub mod record;
mod scope;

use std::{
    error::Error,
    fmt::{self, Debug, Display, Formatter},
};

use ast::ProtoFile;
use grammar::GrammarError;
use lexer::{LexingError, LineLocation, TokenLexer};

pub use error::generate_error_message;
use error::ErrorLocation;

#[cfg(feature = "llvm")]
mod ir;
#[cfg(feature = "llvm")]
pub use inkwell;
#[cfg(feature = "llvm")]
pub use ir::{
    LlvmBitcodeBufferTarget, LlvmIrStringTarget, LlvmModuleTarget, LlvmObjectTarget, LlvmVecTarget,
};
use record::RecordHandler;

pub trait CompileTargetError<Target: CompileTarget>:
    Error + for<'a> Into<CompileError<'a, Target>>
{
    fn location(&self) -> Option<ErrorLocation>;
}

pub trait CompileTarget: Debug + Sized {
    type Result;
    type Error: CompileTargetError<Self>;

    fn compile_ast<'input>(
        &self,
        ast: &'input ProtoFile,
        proto_name: &'input str,
        record_handler: &mut impl RecordHandler,
    ) -> Result<Self::Result, Self::Error>;
}

#[derive(Debug, Clone)]
pub enum CompileAstError<'input> {
    Lexing(LexingError<LineLocation>),
    Grammar(GrammarError<'input>),
}

impl<'input> From<LexingError<LineLocation>> for CompileAstError<'input> {
    fn from(value: LexingError<LineLocation>) -> Self {
        CompileAstError::Lexing(value)
    }
}

impl<'input> From<GrammarError<'input>> for CompileAstError<'input> {
    fn from(value: GrammarError<'input>) -> Self {
        match value {
            GrammarError::Lexing(lexing) => CompileAstError::Lexing(lexing),
            other => CompileAstError::Grammar(other),
        }
    }
}

impl<'input> Display for CompileAstError<'input> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            CompileAstError::Lexing(lexing) => Display::fmt(&lexing, f),
            CompileAstError::Grammar(grammar) => Display::fmt(&grammar, f),
        }
    }
}

impl<'input> Error for CompileAstError<'input> {}

pub fn compile_ast(input: &str) -> Result<ProtoFile, CompileAstError> {
    let lexer = TokenLexer::new(input);
    let ast = ProtoFile::parse(lexer)?;
    Ok(ast)
}

#[derive(Debug, Clone)]
pub enum CompileError<'input, Target: CompileTarget> {
    Lexing(LexingError<LineLocation>),
    Grammar(GrammarError<'input>),
    Compile(Target::Error),
}

impl<'input, Target: CompileTarget> From<CompileAstError<'input>> for CompileError<'input, Target> {
    fn from(value: CompileAstError<'input>) -> Self {
        match value {
            CompileAstError::Lexing(lexing) => CompileError::Lexing(lexing),
            CompileAstError::Grammar(grammar) => CompileError::Grammar(grammar),
        }
    }
}

impl<'input, Target: CompileTarget> Display for CompileError<'input, Target> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            CompileError::Lexing(lexing) => Display::fmt(&lexing, f),
            CompileError::Grammar(grammar) => Display::fmt(&grammar, f),
            CompileError::Compile(compile) => Display::fmt(&compile, f),
        }
    }
}

impl<'input, Target: CompileTarget> Error for CompileError<'input, Target> {}

impl<'input, Target: CompileTarget> CompileError<'input, Target> {
    pub fn location(&self) -> Option<ErrorLocation> {
        match self {
            CompileError::Lexing(error) => error.location(),
            CompileError::Grammar(error) => error.location(),
            CompileError::Compile(error) => error.location(),
        }
    }
}

pub fn compile<'input, Target: CompileTarget>(
    target: &mut Target,
    input: &'input str,
    name: &'input str,
    record_handler: &mut impl RecordHandler,
) -> Result<Target::Result, CompileError<'input, Target>> {
    let ast = compile_ast(input)?;
    target
        .compile_ast(&ast, name, record_handler)
        .map_err(|err| err.into())
}
